﻿using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using System.Security.Cryptography;
using System.Text;

namespace Infrastructure.Utils
{
    /// <summary>
    /// token 创建和验证
    /// </summary>
    public class TokenBuilder
    {
        private readonly TokenOptions _tokenSetting;

        public TokenBuilder(IOptions<TokenOptions> tokenSetting)
        {
            _tokenSetting = tokenSetting.Value;
        }
        /// <summary>
        /// 创建jwttoken
        /// </summary>
        /// <param name="payLoad">payLoad</param>
        /// <param name="expiresMinute">过期时间[默认120min]</param>
        /// <param name="header"></param>
        /// <returns></returns>
        public string CreateToken(Dictionary<string, object> payLoad, double expiresMinute = 120, Dictionary<string, object> header = null)
        {
            if (header == null)
            {
                header = new Dictionary<string, object> { { "alg", "HS256" }, { "typ", "JWT" } };
            }
            var now = DateTime.UtcNow;
            payLoad["nbf"] = ToUnixEpochDate(now);//可用时间起始
            payLoad["exp"] = ToUnixEpochDate(now.Add(TimeSpan.FromMinutes(expiresMinute)));//可用时间结束

            var encodedHeader = Base64UrlEncoder.Encode(JsonConvert.SerializeObject(header));
            var encodedPayload = Base64UrlEncoder.Encode(JsonConvert.SerializeObject(payLoad));

            var hs256 = new HMACSHA256(Encoding.UTF8.GetBytes(_tokenSetting.secret));
            var encodedSignature = Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(encodedHeader, ".", encodedPayload))));

            var encodedJwt = string.Concat(encodedHeader, ".", encodedPayload, ".", encodedSignature);
            return encodedJwt;
        }

        /// <summary>
        /// 验证token
        /// </summary>
        /// <param name="encodeJwt">token</param>
        /// <param name="validatePayLoad">自定义验证函数</param>
        /// <returns></returns>
        public bool Verify(string encodeJwt, Func<Dictionary<string, object>, bool> validatePayLoad = null)
        {
            if (string.IsNullOrWhiteSpace(encodeJwt)) return false;
            encodeJwt = encodeJwt.Replace("Bearer ", "");
            validatePayLoad ??= ((load) => { return true; });
            var success = true;
            var jwtArr = encodeJwt.Split('.');
            if (jwtArr.Length != 3) return false;
            //var header = JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[0]));
            var payLoad = JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[1]));

            var hs256 = new HMACSHA256(Encoding.UTF8.GetBytes(_tokenSetting.secret));
            //1.验证签名
            success = success && string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1])))));
            if (!success)
            {
                return false;
            }
            //2.验证有效期
            var now = ToUnixEpochDate(DateTime.UtcNow);
            success = success && (now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString()));

            //3.自定义验证
            success = success && validatePayLoad(payLoad);

            return success;
        }
        /// <summary>
        /// 获取过期时间：分钟
        /// </summary>
        /// <returns></returns>
        public long? ExpSeccond(string encodeJwt, Func<Dictionary<string, object>, bool> validatePayLoad = null)
        {
            if (string.IsNullOrWhiteSpace(encodeJwt)) return null;
            encodeJwt = encodeJwt.Replace("Bearer ", "");
            validatePayLoad ??= ((load) => { return true; });
            var success = true;
            var jwtArr = encodeJwt.Split('.');
            if (jwtArr.Length != 3) return null;
            var payLoad = JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[1]));

            var hs256 = new HMACSHA256(Encoding.UTF8.GetBytes(_tokenSetting.secret));
            //1.验证签名
            success = success && string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1])))));
            if (!success) return null;

            //2.验证有效期
            var now = ToUnixEpochDate(DateTime.UtcNow);
            // success = success && (now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString()));
            var res = now - long.Parse(payLoad["exp"].ToString());
            if (res < 0) return null;
            return res;
        }
        /// <summary>
        /// 获取jwt中的payLoad
        /// </summary>
        /// <param name="encodeJwt"></param>
        /// <returns></returns>
        public static Dictionary<string, object> GetPayLoad(string encodeJwt)
        {
            try
            {
                var jwtArr = encodeJwt.Split('.');
                if (jwtArr.Length != 3) return null;
                var payLoad = JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[1]));
                return payLoad;
            }
            catch
            {
                return null;
            }
        }
        private static long ToUnixEpochDate(DateTime date) =>
            (long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds);
    }

    public class TokenOptions
    {
        public string secret { get; set; }
        /// <summary>
        /// access_token_Minute、access_token使用有效期
        /// </summary>
        public int access_token_minute { get; set; }
        /// <summary>
        /// refresh_token_Minute、登录一次最多保持时间
        /// </summary>
        public int refresh_token_minute { get; set; }
        /// <summary>
        /// access_token过期多久内可以使用refresh_token
        /// </summary>
        public int refresh_token_limit_minute { get; set; }
    }

    public class VertiryInfo
    {
        public bool IsValid { get; set; }
        public int ExpMinute { get; set; }
    }
}
